/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
*/

package com.chasekernan.hxnova.core.components;

import haxe.xml.Check;

import com.chasekernan.hxnova.core.components.Component;
import com.chasekernan.hxnova.core.components.Armor;
import com.chasekernan.hxnova.core.components.BeamWeapon;
import com.chasekernan.hxnova.core.components.Bomb;
import com.chasekernan.hxnova.core.components.Engine;
import com.chasekernan.hxnova.core.components.Defense;
import com.chasekernan.hxnova.core.components.Scanner;
import com.chasekernan.hxnova.core.components.Shield;
import com.chasekernan.hxnova.core.components.Missile;
import com.chasekernan.hxnova.core.components.CargoPod;

/**
    Holds all of the components and sorts them by type.
**/
class ComponentSet {
    
    /**
        Contains the classes of all component types (armor, beam weapon etc).
    **/
    public static var COMPONENT_TYPES : Array<Dynamic> = 
            [Armor, BeamWeapon, Bomb, CargoPod, Colonizer, Component, Computer, 
             Defense, Engine, Hull, Missile, Scanner, StarScanner, Shield];
    
    /**
        Holds all of the components into arrays based on type.
    **/
    public static var ALL_COMPONENTS : Array<Array<Dynamic>> = setupAllComp();
    
    /**
        Contains all the ComponentTypeSets (different from component types).
    **/
    public static var COMPONENT_TYPE_SETS : Array<ComponentTypeSet> = setupCTS();
    
    private static function setupCTS() : Array<ComponentTypeSet> {
        var a = new Array<ComponentTypeSet>();
        
        var weap = new ComponentTypeSet("Weapon");
        weap.addType(BeamWeapon); weap.addType(Bomb); weap.addType(Missile);
        a.push(weap);
        
        var eng : ComponentTypeSet = new ComponentTypeSet("Engine");
        eng.addType(Engine);
        a.push(eng);
        
        return a;
    }
    
    public static function initialSetup() {
        addComponent(new Scanner("None", 0));
    }
    
    private static function setupAllComp() : Array<Array<Dynamic>> {
        var a = new Array<Array<Dynamic>>();
        for(i in 0...COMPONENT_TYPES.length) a.push(new Array<Dynamic>());
        return a;
    }
    
    /**
        Adds a component type set.
        Will throw an error if it has the same name as another.
    **/
    public static function addComponentTypeSet(c : ComponentTypeSet) {
        for(i in COMPONENT_TYPE_SETS) {
            if(i.name == c.name) throw "Cannot add 2 component type sets of " +
                    "the same name.";
        }
        COMPONENT_TYPE_SETS.push(c);
    }
    
    /**
        Returns a type set by name.
        Will throw an error if there is no type set by the given name.
    **/
    public static function getComponentTypeSetByName(name : String) : 
            ComponentTypeSet {
        for(i in COMPONENT_TYPE_SETS) {if(i.name == name) return i;}
        throw "No such component type set by name "+name;
    }
    
    public static function removeComponentTypeSet(c : ComponentTypeSet) {
        for(i in 0...COMPONENT_TYPE_SETS.length) {
            if(COMPONENT_TYPE_SETS[i] == c) {
                COMPONENT_TYPE_SETS.splice(i, 1);
            }
        }
    }
    
    /**
        Sets an existing component with the same name as [c] equal to c.
        Will throw an error if the components are not of the same type or if 
        there is no component with the name [c].
    **/
    public static function setComponent(c : Component) {
        for(i in ALL_COMPONENTS) {
            for (j in 0...i.length) {
                var nc = cast(i[j], Component);
                if (nc.name == c.name) {
                    if (Type.getClass(c) != Type.getClass(nc)) 
                        throw "Components are not of the same type.";
                    i[j] = c;
                    return;
                }
            }
        }
        
        throw "No component named " + c.name;
    }
    
    public static function addComponent(c : Component) : Void {
        var type = Type.getClass(c);
        for(i in 0...COMPONENT_TYPES.length) {
            if(COMPONENT_TYPES[i] == type) {ALL_COMPONENTS[i].push(c); return;}
        }
        
        throw "No such component type (probably need to update " + 
                "COMPONENT_TYPES array: "+c;
    }
    
    /**
        Returns a component with the give name.
        Will throw an error if there is no such component with the given name.
    **/
    public static function getComponentByName(name : String) : Component {
        for(i in ALL_COMPONENTS) {
            for(j in i) {
                var c = cast(j, Component);
                if(c.name == name) return c;
            }
        }
        
        throw "No compnent named "+name;
    }
    
    /**
        Same as [getComponentByName] but also specifies a type and if the type
        doesn't match, then it will throw an error.
    **/
    public static function getComponent(name : String, type : Class<Dynamic>) : 
            Component {
        for(i in 0...COMPONENT_TYPES.length) {
            if(COMPONENT_TYPES[i] == type) {
                for(j in ALL_COMPONENTS[i]) {
                    var c = cast(j, Component);
                    if(c.name == name) {
                        return c;
                    }
                }
            }
        }
        
        throw "No component of type " + type + " and name " + name + ".";
    }
    
    /**
        Returns an array of all the components of type [type].
        Will throw an error if there is no component type [type].
    **/
    public static function getAllComponentsOfType(type : Class<Dynamic>) : 
            Array<Component> {
        for(i in 0...COMPONENT_TYPES.length) {
            if(COMPONENT_TYPES[i] == type) {
                var a : Array<Component> = cast ALL_COMPONENTS[i];
                return a;
            }
        }
        
        throw "No component type "+type;
    }
    
    /**
        Removes the given component from the list, but will throw an error if
        there is no such component on the list.
    **/
    public static function removeComponent(c : Component) {
        for(i in 0...ALL_COMPONENTS.length) {
            for(j in 0...ALL_COMPONENTS[i].length) {
                var a : Component = cast ALL_COMPONENTS[i][j];
                if(a == c) {
                    ALL_COMPONENTS[i].splice(j, 1);
                    return;
                }
            }
        }
        
        throw "No such component: "+c;
    }
    
    /**
        Same as [removeComponent], but specified by name instead.
    **/
    public static function removeComponentByName(name : String) {
        removeComponent(getComponentByName(name));
    }
    
    /**
        Returns the xml formated name of class.
        Example:
            com.chasekernan.hxnova.core.components.BeamWeapon -> beam_weapon
    **/
    public static function getXmlName(c : Class<Dynamic>) : String {
        var className = Type.getClassName(c);
        var start = className.lastIndexOf(".") + 1;
        return Component.getUnderscoreVersion([className.substr(start)])[0];
    }
    
    public static var XML_TITLE = "component_set";
    
    public static function getRules() : Rule {
        var children = new Array<Rule>();
        for (i in COMPONENT_TYPES) {
            children.push(RNode(getXmlName(i) + "s", [], 
                RMulti(Type.createEmptyInstance(i).getRules(), false)));
        }
        children.push(RNode('component_type_sets', [], 
                RMulti(ComponentTypeSet.getXmlRules(), false)));
        
        return RNode(XML_TITLE, [], RList(children, false));
    }
    
    /**
        Writes the components and component type sets to xml.
        Example:
            TODO: run [writeToXml] and insert xml here.
    **/
    public static function writeToXml() : Xml {
        var xml = Xml.parse('<' + XML_TITLE + "></" + XML_TITLE + ">")
                .firstChild();
        
        for(i in 0...COMPONENT_TYPES.length) {
            var child = Xml.createElement(getXmlName(COMPONENT_TYPES[i]) + "s");
            for(j in ALL_COMPONENTS[i]) {
                var comp = cast (j, Component);
                child.addChild(COMPONENT_TYPES[i].writeToXml(comp));
            }
            xml.addChild(child);
        }
        
        var ctsXml = Xml.createElement('component_type_sets');
        for(i in COMPONENT_TYPE_SETS) {
            ctsXml.addChild(ComponentTypeSet.writeToXml(i));
        }
        xml.addChild(ctsXml);
        
        //Check.checkNode(xml, getRules());
        
        return xml;
    }
    
    /**
        Reads the components from xml.
        Will throw an error if there are any mistakes.
    **/
    public static function readFromXml(xml : Xml) {
        try {
            //TODO: Get check working for components.
            //Check.checkNode(xml, getRules());
            
            ALL_COMPONENTS = setupAllComp();
            COMPONENT_TYPE_SETS = new Array<ComponentTypeSet>();
            
            for(i in xml.elementsNamed('component_type_sets').next().elements()) 
                COMPONENT_TYPE_SETS.push(ComponentTypeSet.readFromXml(i));
            
            
            for(i in xml.elements()) {
                
                for(j in COMPONENT_TYPES) {
                    if((getXmlName(j) + "s") == i.nodeName) {
                        for(k in i.elements()) 
                            addComponent(j.readFromXml(k));
                        break;
                    }
                }
            }
        } catch (e : Dynamic) {
            throw "Error while reading xml file for component set: " + e + ".";
        }
    }
    
    /**
        Returns the component class based upon the xml name
        Example:
            beam_weapon -> com.chasekernan.hxnova.core.components.BeamWeapon.
        Will throw an erro if there is no such component type.
    **/
    public static function getClassFromXmlName(name : String) : Class<Dynamic> {
        for(i in COMPONENT_TYPES) {
            if (getXmlName(i) == name) return untyped i;
        }
        
        throw "No such component type: "+name;
    }
}